﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Media;
using System.Runtime.InteropServices;
using System.Runtime.ConstrainedExecution;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Windows.Interop;

using Heaven.Mathematics;
using Heaven.Graphics;
using Heaven.Geometry;

using Color = Heaven.Graphics.Color;
using VertexElement = Heaven.Graphics.VertexElement;
using Point = System.Drawing.Point;
using RenderTarget = Heaven.Graphics.RenderTarget;

using Direct3D9 = SlimDX.Direct3D9;
using Slimdx = SlimDX;

namespace Heaven.SlimDX.Graphics
{
    /// <summary>
    /// Implementation of SlimDX device that is compatible with 
    /// Windows Presentation Foundation .NET subsystem
    /// </summary>
    public class WindowsPresentationDevice : CriticalFinalizerObject, IDevice
    {
        #region Events

        
                
        #endregion

        #region Direct3D Fields
        
        // Direct3D9 graphics device
        Direct3D9.Device device = null;        
        // Direct3D object        
        Direct3D9.Direct3D direct3D = null;        
        // Caches device representaions of polygonal objects
        PolygonalManager polygonalManager = null;        
        // Caches device representaions of texture objects
        TextureManager textureManager = null;
        // Caches shader objects        
        ShaderManager shaderManager = null;
        // Invisible dummy window
        HwndSource hwnd = null;

        // Back buffer
        Direct3D9.Surface backBuffer = null;
        Direct3D9.Surface depthSurface = null;
        Slimdx.Viewport viewport;

        #endregion

        #region Fields

        /// <summary>
        /// Render method (for ex., when 
        /// inner buffer has changed size)
        /// </summary>
        MethodInfo renderingMethod;
        WeakReference renderingObject;
                
        // Default color
        Slimdx.Color4 defaultColor = new Slimdx.Color4(1,1,1,1);
        // Default depth value
        double defaultDepth = 1.0f;
        
        // Current setted render target
        RenderTarget currentRenderTarget = null;
        
        // Target image
        Image target = null;
        D3DImage d3dimage = null;

        // User defined size
        Heaven.Graphics.Size size = new Heaven.Graphics.Size(Double.NaN, Double.NaN);
        // Flag to prevent unnecessary redrawings
        bool skipInvalidatings = false;

        // Timer to resize content
        DispatcherTimer resizeTimer;
        bool needResize = false;

        // Priority to be using to dispatch render cycle
        DispatcherPriority priority = DispatcherPriority.Input;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the priority to be using to dispatch render cycle
        /// </summary>
        public DispatcherPriority Priority 
        {
            get { return priority; }
            set { priority = value; } 
        }

        /// <summary>
        /// Default color
        /// </summary>
        public Color DefaultColor 
        {
            get { return Converter.Convert(defaultColor); }
            set { defaultColor = Converter.Convert(value); } 
        }

        /// <summary>
        /// Default depth value
        /// </summary>
        public double DefaultDepth 
        {
            get { return defaultDepth; }
            set { defaultDepth = value; }
        }

        /// <summary>
        /// Current render target
        /// </summary>
        public RenderTarget RenderTarget 
        {
            get 
            {
                return currentRenderTarget;
            }
            set
            {
                if (currentRenderTarget == value) return;

                // Resolving previous render target
                if (currentRenderTarget == null) device.EndScene();
                else textureManager.GetRenderTarget(currentRenderTarget).EndRendering();
 
                if (value == null)
                {
                    // Get back to main buffer
                    currentRenderTarget = null;
                    // Set render target and depth buffer
                    device.SetRenderTarget(0, backBuffer);
                    device.DepthStencilSurface = depthSurface;
                    device.Viewport = viewport;
                    device.BeginScene();
                    return;
                }
                else
                {                                        
                    if(currentRenderTarget != null) currentRenderTarget.Invalidate();                                        
                    currentRenderTarget = value;
                    textureManager.GetRenderTarget(currentRenderTarget).BeginRendering();
                }
            }
        }

        
        /// <summary>
        /// Gets or sets resolution of the internal buffers (NaN means auto)
        /// </summary>
        public Heaven.Graphics.Size Resolution 
        {
            get { return size; }
            set
            {
                size = value;
                needResize = true;
                Invalidate();
            }
        }

        #endregion

        #region Initialization
        
        /// <summary>
        /// Default constructor
        /// </summary>
        /// <param name="pool">Device pool (the same resources will be using by different devices in the pool)</param>
        /// <param name="rendering">Render method</param>
        /// <param name="target">Target image</param>
        public WindowsPresentationDevice(Image target, ThreadStart rendering)
        {
            this.renderingMethod = rendering.Method;
            this.renderingObject = new WeakReference(rendering.Target);
            this.target = target;
            this.target.SizeChanged += new SizeChangedEventHandler(OnTargetSizeChanged);
            this.target.Source = BitmapImage.Create(1, 1, 96, 96, PixelFormats.Bgra32, null, new byte[] { 0, 0, 0, 0 }, 4);

            resizeTimer = new DispatcherTimer(DispatcherPriority.SystemIdle);
            resizeTimer.Interval = TimeSpan.FromSeconds(1);
            resizeTimer.Tick += new EventHandler(OnResizeTimerTick);

            d3dimage = new D3DImage();
            d3dimage.IsFrontBufferAvailableChanged += OnD3dimageIsFrontBufferAvailableChanged;
            

            if (target.IsLoaded) Invalidate();
            else target.Loaded += new RoutedEventHandler(OnTargetLoaded);
        }

        void OnResizeTimerTick(object sender, EventArgs e)
        {
            resizeTimer.Stop();
            needResize = true;
            Invalidate();
        }

        void OnTargetSizeChanged(object sender, SizeChangedEventArgs e)
        {
            resizeTimer.Stop();
            resizeTimer.Start();
        }

        void OnTargetLoaded(object sender, RoutedEventArgs e)
        {
            RenderCore();
            target.Loaded -= new RoutedEventHandler(OnTargetLoaded);
        }

        void CreateBuffers(int width, int height)
        {
            // Performance consideration: on XP we need lockable 
            // surface to make hardware update, on Vista it's 
            // never mind, but not-lockable faster in general
            if (Environment.OSVersion.Version.Major < 6)
            {                
                backBuffer = Direct3D9.Surface.CreateRenderTarget(device,
                    width, height,
                    Direct3D9.Format.A8R8G8B8,
                    Direct3D9.MultisampleType.None, 0, true); // FIXME: false <-> true?
                depthSurface = Direct3D9.Surface.CreateDepthStencil(device,
                    width, height,
                    Direct3D9.Format.D24X8, Direct3D9.MultisampleType.None, 0, true);
            }
            else
            {
                backBuffer = Direct3D9.Surface.CreateRenderTargetEx((Direct3D9.DeviceEx)device,
                    width, height,
                    Direct3D9.Format.A8R8G8B8,
                    Direct3D9.MultisampleType.None, 0, true, Direct3D9.Usage.None);
                depthSurface = Direct3D9.Surface.CreateDepthStencilEx((Direct3D9.DeviceEx)device,
                    width, height,
                    Direct3D9.Format.D24X8, Direct3D9.MultisampleType.None, 0, true, Direct3D9.Usage.None);                
            }

            viewport = new Slimdx.Viewport(0, 0, width, height, 0, 1);
        }

        /// <summary>
        /// Disposes internal buffers
        /// </summary>
        internal void DisposeBuffers()
        {   
            if (backBuffer != null) backBuffer.Dispose();
            if (depthSurface != null) depthSurface.Dispose();
                       
            backBuffer = null;
            depthSurface = null;
        }

        #endregion

        #region Shading

        /// <summary>
        /// Finds the parameter by semantic name
        /// </summary>
        /// <param name="shader">Shader</param>
        /// <param name="semantic">Semantic</param>
        /// <returns>Parameter name or null if the parameter is not found</returns>
        public string GetParameterBySemantic(Shader shader, string semantic)
        {
            Direct3D9.Effect effect = shaderManager.Get(shader);
            Direct3D9.EffectHandle parameter = effect.GetParameterBySemantic(null, semantic);
            return parameter == null ? null : effect.GetParameterDescription(parameter).Name;
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, bool value)
        {
            try
            {
                shaderManager.Get(shader).SetValue(name, value);
            }
            catch { }
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, bool[] value)
        {
            try
            {
                shaderManager.Get(shader).SetValue(name, value);
            }
            catch { }
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, float value)
        {
            try { shaderManager.Get(shader).SetValue(name, value); }
            catch { }
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, float[] value)
        {
            try { shaderManager.Get(shader).SetValue(name, value); }
            catch { }
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, int value)
        {
            try { shaderManager.Get(shader).SetValue(name, value); }
            catch { }
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, int[] value)
        {
            try { shaderManager.Get(shader).SetValue(name, value); }
            catch { }
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Heaven.Texturing.Texture value)
        {
            try { shaderManager.Get(shader).SetTexture(name, textureManager.Get(value)); }
            catch { }
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Color value)
        {
            try { shaderManager.Get(shader).SetValue(name, Converter.ConvertToVector4(value)); }
            catch { }
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Color[] value)
        {
            try
            {
                Slimdx.Vector4[] colors = new Slimdx.Vector4[value.Length];
                for (int i = 0; i < value.Length; i++)
                {
                    colors[i] = Converter.ConvertToVector4(value[i]);
                }
                shaderManager.Get(shader).SetValue(name, colors);
            }
            catch { }
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Matrix4 value)
        {
            try { shaderManager.Get(shader).SetValue(name, Converter.Convert(value)); }
            catch { }
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Matrix4[] value)
        {
            try
            {
                Slimdx.Matrix[] matrices = new Slimdx.Matrix[value.Length];
                for (int i = 0; i < value.Length; i++)
                {
                    matrices[i] = Converter.Convert(value[i]);
                }

                shaderManager.Get(shader).SetValue(name, matrices);
            }
            catch { }
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Vector4 value)
        {
            try { shaderManager.Get(shader).SetValue(name, Converter.Convert(value)); }
            catch { }
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Vector3 value)
        {
            try { shaderManager.Get(shader).SetValue(name, Converter.Convert(value)); }
            catch { }
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Vector4[] value)
        {
            try
            {
                Slimdx.Vector4[] vecs = new Slimdx.Vector4[value.Length];
                for (int i = 0; i < value.Length; i++)
                {
                    vecs[i] = Converter.Convert(value[i]);
                }
                shaderManager.Get(shader).SetValue(name, vecs);
            }
            catch { }
        }

        #endregion

        #region Rendering

        /// <summary>
        /// Renders the image immediately
        /// </summary>
        public void RenderOffscreen()
        {
            #region Create device if it is required

            if (device == null)
            {
                // Create offscreen window            
                if (hwnd == null) hwnd = new HwndSource(0, 0, 0, 0, 0, "", IntPtr.Zero);
                // Set parameters
                Direct3D9.PresentParameters presentationParameters = new Direct3D9.PresentParameters();
                presentationParameters.AutoDepthStencilFormat = Direct3D9.Format.D24X8;
                presentationParameters.BackBufferCount = 1;
                presentationParameters.BackBufferFormat = Direct3D9.Format.X8R8G8B8;
                presentationParameters.BackBufferHeight = 2;
                presentationParameters.BackBufferWidth = 2;
                presentationParameters.DeviceWindowHandle = hwnd.Handle;
                presentationParameters.EnableAutoDepthStencil = true;
                presentationParameters.Windowed = true;
                presentationParameters.SwapEffect = Direct3D9.SwapEffect.Discard;
                presentationParameters.Multisample = Direct3D9.MultisampleType.None;
                presentationParameters.MultisampleQuality = 0;
                presentationParameters.PresentationInterval = Direct3D9.PresentInterval.Immediate;
                presentationParameters.PresentFlags = Direct3D9.PresentFlags.DiscardDepthStencil;

                // Create device
                if (Environment.OSVersion.Version.Major >= 6)
                {
                    direct3D = new Direct3D9.Direct3DEx();
                    device = new Direct3D9.DeviceEx((Direct3D9.Direct3DEx)direct3D, 0, Direct3D9.DeviceType.Hardware, hwnd.Handle,
                          Direct3D9.CreateFlags.FpuPreserve | Direct3D9.CreateFlags.HardwareVertexProcessing |
                          Direct3D9.CreateFlags.Multithreaded, presentationParameters);
                }
                else
                {
                    direct3D = new Direct3D9.Direct3D();
                    device = new Direct3D9.Device(direct3D, 0, Direct3D9.DeviceType.Hardware, hwnd.Handle,
                         Direct3D9.CreateFlags.FpuPreserve | Direct3D9.CreateFlags.HardwareVertexProcessing |
                         Direct3D9.CreateFlags.Multithreaded, presentationParameters);
                }

                // Texture cache
                textureManager = new TextureManager(device);
                shaderManager = new ShaderManager(device);
                polygonalManager = new PolygonalManager();
                needResize = true;
            }

            #endregion

            #region Handle resizing

            if (needResize)
            {   
                DisposeBuffers();
                if (Double.IsNaN(size.Width))
                {
                    if (target.ActualWidth == 0) CreateBuffers(512, 512);
                    else CreateBuffers((int)target.ActualWidth, (int)target.ActualHeight);
                }
                else CreateBuffers((int)size.Width, (int)size.Height);                
            }

            #endregion

            InvokeUserRendering();
        }

        void InvokeUserRendering()
        {
            object userObject = renderingObject.Target;
            if (userObject != null) renderingMethod.Invoke(userObject, new object[] {});
        }

        FieldInfo _isDirty = null;

        bool IsD3DImageReady()
        {
            if(_isDirty == null)
            {
                Type d3dImageType = d3dimage.GetType();
                _isDirty = d3dImageType.GetField("_isDirty", BindingFlags.NonPublic | BindingFlags.Instance);
            }
            bool dirty = (bool)(_isDirty.GetValue(d3dimage));
            return !dirty;
        }

        void RenderCore()
        {
            if ((target.ActualWidth == 0) || (!IsD3DImageReady()))
            {
                // Not loaded yet?
                if (!target.IsLoaded)
                {
                    // Wait new invalidatings
                    skipInvalidatings = false;
                    target.Loaded += OnTargetLoaded;
                    return;
                }

                // WPF don't ready yet
                skipInvalidatings = false;
                Invalidate();
                return;
            }

            if (!d3dimage.IsFrontBufferAvailable) return;
            if (target.Source != d3dimage) target.Source = d3dimage;

            d3dimage.Lock();

            // Wait new invalidatings
            skipInvalidatings = false;

            #region Create device if it is required

            if (device == null)
            {
                // Create offscreen window            
                if(hwnd == null) hwnd = new HwndSource(0, 0, 0, 0, 0, "", IntPtr.Zero);
                // Set parameters
                Direct3D9.PresentParameters presentationParameters = new Direct3D9.PresentParameters();
                presentationParameters.AutoDepthStencilFormat = Direct3D9.Format.D24X8;
                presentationParameters.BackBufferCount = 1;
                presentationParameters.BackBufferFormat = Direct3D9.Format.X8R8G8B8;
                presentationParameters.BackBufferHeight = 2;
                presentationParameters.BackBufferWidth = 2;
                presentationParameters.DeviceWindowHandle = hwnd.Handle;
                presentationParameters.EnableAutoDepthStencil = true;
                presentationParameters.Windowed = true;
                presentationParameters.SwapEffect = Direct3D9.SwapEffect.Discard;
                presentationParameters.Multisample = Direct3D9.MultisampleType.None;
                presentationParameters.MultisampleQuality = 0;
                presentationParameters.PresentationInterval = Direct3D9.PresentInterval.Immediate;
                presentationParameters.PresentFlags = Direct3D9.PresentFlags.DiscardDepthStencil;

                // Create device
                if (Environment.OSVersion.Version.Major >= 6)
                {
                    direct3D = new Direct3D9.Direct3DEx();
                    device = new Direct3D9.DeviceEx((Direct3D9.Direct3DEx)direct3D, 0, Direct3D9.DeviceType.Hardware, hwnd.Handle,
                          Direct3D9.CreateFlags.FpuPreserve | Direct3D9.CreateFlags.HardwareVertexProcessing |
                          Direct3D9.CreateFlags.Multithreaded, presentationParameters);
                }
                else
                {
                    direct3D = new Direct3D9.Direct3D();
                    device = new Direct3D9.Device(direct3D, 0, Direct3D9.DeviceType.Hardware, hwnd.Handle,
                         Direct3D9.CreateFlags.FpuPreserve | Direct3D9.CreateFlags.HardwareVertexProcessing |
                         Direct3D9.CreateFlags.Multithreaded, presentationParameters);
                }

                // Texture cache
                textureManager = new TextureManager(device);
                shaderManager = new ShaderManager(device);
                polygonalManager = new PolygonalManager();
                needResize = true;
            }

            #endregion
            
            #region Handle resizing

            if (needResize)
            {
                needResize = false;
                DisposeBuffers();
                if (Double.IsNaN(size.Width))
                {
                    if (target.ActualWidth == 0) CreateBuffers(1024, 1024);
                    else CreateBuffers((int)target.ActualWidth, (int)target.ActualHeight);
                }
                else CreateBuffers((int)size.Width, (int)size.Height);
                d3dimage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, backBuffer.ComPointer);

                // I don't know why, but after resizing 
                // sometimes image disappears
                Invalidate();
            }

            #endregion
                        
            try
            {
                InvokeUserRendering();
            }
            catch { }            
                      
            // Show new image
            d3dimage.AddDirtyRect(new Int32Rect(0,0, d3dimage.PixelWidth, d3dimage.PixelHeight));
            d3dimage.Unlock();
        }

        /// <summary>
        /// Begins rendering
        /// </summary>
        public void Begin()
        {            
            // Set render target and depth buffer
            device.SetRenderTarget(0, backBuffer);
            device.DepthStencilSurface = depthSurface;
            device.Viewport = viewport;
            
            // Performs rendering
            device.BeginScene();
            device.Clear(global::SlimDX.Direct3D9.ClearFlags.Target, new global::SlimDX.Color4(0.5f, 0.5f, 0.9f, 0.5f), 1.0f, 0);
        }

        /// <summary>
        /// Ends of rendering process, swaps front and back buffers
        /// </summary>
        public void Present()
        {
             device.EndScene();
        }
        
        /// <summary>
        /// Invalidates the contents and 
        /// invoke RenderRequest to render new content
        /// </summary>       
        public void Invalidate()
        {
            if (skipInvalidatings) return;
            skipInvalidatings = true;
            //target.Dispatcher.BeginInvoke((ThreadStart)RenderCore,  DispatcherPriority.Input/* priority*/);
            CompositionTarget.Rendering += OnCompositionTargetRendering;
        }

        void OnCompositionTargetRendering(object sender, EventArgs e)
        {
            CompositionTarget.Rendering -= OnCompositionTargetRendering;
            RenderCore();
        }

        void OnD3dimageIsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            // if the front buffer is available, then WPF has just created a new
            // D3D device, so we need to start rendering our custom scene
            if (d3dimage.IsFrontBufferAvailable)
            {                
                RenderCore();
                Invalidate();
            }
            else
            {
                // Consider D3DImage bug, it helps a little...
                System.Threading.Thread.Sleep(1000);
                // If the front buffer is no longer available, then WPF has lost its
                // D3D device so there is no reason to waste cycles rendering our
                // custom scene until a new device is created.
                Destruct();
            }
        }

        /// <summary>
        /// Clears depth and frame
        /// </summary>
        public void Clear()
        {
            device.Clear(Direct3D9.ClearFlags.Target | Direct3D9.ClearFlags.ZBuffer, defaultColor, (float)defaultDepth, 0);
        }

        /// <summary>
        /// Clears depth
        /// </summary>
        public void ClearDepth()
        {
            device.Clear(Direct3D9.ClearFlags.ZBuffer, defaultColor, (float)defaultDepth, 0);
        }

        /// <summary>
        /// Clears frame
        /// </summary>
        public void ClearFrame()
        {
            device.Clear(Direct3D9.ClearFlags.Target, defaultColor, (float)defaultDepth, 0);
        }

        /// <summary>
        /// Draws a polygonal object
        /// </summary>
        /// <param name="polygonal">Polygonal object</param>
        /// <param name="shader">Shader object</param>
        public void Draw(Polygonal polygonal, Shader shader)
        {
            if (polygonal.IsEmpty) return;

            Slimdx.Result result;

            Direct3D9.Effect effect = shaderManager.Get(shader);
            Direct3D9.EffectHandle technique = effect.GetTechnique(0);
            effect.Technique = technique; // ?
            int passCount = effect.Begin(Direct3D9.FX.DoNotSaveState | Direct3D9.FX.DoNotSaveShaderState | Direct3D9.FX.DoNotSaveSamplerState);
            try
            {                
                for (int i = 0; i < passCount; i++)
                {
                    result = effect.BeginPass(i);
                    polygonalManager.Draw(device, polygonal);                                      
                    result = effect.EndPass();
                }
            }
            finally
            {
                effect.End();
            }
        }

        #endregion

        #region Destructor

        /// <summary>
        /// Destructor
        /// </summary>
        ~WindowsPresentationDevice()
        {
            Destruct();
        }

        /// <summary>
        /// Forces disposing this device immediately 
        /// </summary>
        public void Dispose()
        {
            GC.SuppressFinalize(this);
            Destruct();
        }

        // Disposes unmanaged resources
        void Destruct()
        {
            DisposeBuffers();

            if (device == null) return;

            polygonalManager.Dispose();
            polygonalManager = null;
            textureManager.Dispose();
            textureManager = null;
            shaderManager.Dispose();
            shaderManager = null;

            device.Dispose();
            direct3D.Dispose();
            device = null;
            direct3D = null;
        }

        #endregion

        #region Debug

        static int timeStamp = 0;
        static int frameCount = 0;
        static Dictionary<string, int> objectTable = null;

        /// <summary>
        /// Shows monitor with statistics
        /// </summary>
        public static void ShowMonitor()
        {
            objectTable = new Dictionary<string,int>();
            Slimdx.ObjectTable.ObjectAdded += new Slimdx.ObjectTableNotification(OnObjectTableAdded);
            Slimdx.ObjectTable.ObjectRemoved += new Slimdx.ObjectTableNotification(OnObjectTableRemoved);
            DispatcherTimer timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromSeconds(0.6);
            timer.Tick += new EventHandler(OnMonitorUpdate);
            timer.Start();
        }

        static void OnObjectTableAdded(object sender, Slimdx.ObjectTableEventArgs e)
        {
            lock (objectTable)
            {
                string name = e.ComObject.GetType().Name;
                if (objectTable.ContainsKey(name)) objectTable[name] = objectTable[name] + 1;
                else objectTable.Add(name, 1);
            }
        }

        static void OnObjectTableRemoved(object sender, Slimdx.ObjectTableEventArgs e)
        {
            lock (objectTable)
            {
                string name = e.ComObject.GetType().Name;
                if (objectTable.ContainsKey(name))
                {
                    objectTable[name] = objectTable[name] - 1;
                    if (objectTable[name] == 0) objectTable.Remove(name);
                }
            }
        }
        
        static void OnMonitorUpdate(object sender, EventArgs e)
        {
            // Build object table
            string objectTableText = "Object table:";
            lock (objectTable)
            {
                
                foreach (var obj in objectTable)
                {
                    objectTableText += "\n" + obj.Key + " (" + obj.Value + ")";
                }
                if (objectTable.Count == 0) objectTableText = "";
            }

            // Calculate FPS
            int newTimeStamp = Environment.TickCount;
            int timeDelta = newTimeStamp - timeStamp;
            int fps = timeDelta == 0 ? 0:(int)((((double)frameCount) / (double)(timeDelta)) * 1000.0);
            timeStamp = newTimeStamp;
            frameCount = 0;

            string text = String.Format("Managed memory: {0:0.#}Mb\nMemory allocated: {1:0.#}Mb\nWorking set: {2:0.#}Mb\nFrame per second: {3:0}\n\n{4}",
                (double)((double)GC.GetTotalMemory(false) / (1024.0*1024.0)),
                (double)((double)System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64 / (1024.0 * 1024.0)),
                (double)((double)System.Diagnostics.Process.GetCurrentProcess().WorkingSet64 / (1024.0 * 1024.0)),
                fps,
                objectTableText);
            Debug.DebugMonitor.Show(text);            
        }

        #endregion
    }
}
